﻿/***************************************************************************
 *  Project: WebControlSample
 *  File:    WebDocument.cs
 *  Version: 1.0.0.0
 *
 *  Copyright ©2012 Perikles C. Stephanidis; All rights reserved.
 *  This code is provided "AS IS" without warranty of any kind.
 *__________________________________________________________________________
 *
 *  Notes:
 *
 *  Represents the contents of a tab in an application window. This control
 *  contains the WebControl and an independent bar with the address-box,
 *  navigation buttons etc..
 *   
 ***************************************************************************/

#region Using
using System;
using System.Linq;
using System.IO;
using System.Net;
using Awesomium.Core;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;
using System.Threading.Tasks;
using WeifenLuo.WinFormsUI.Docking;
using System.Collections.Generic;
#endregion

namespace WebControlSample
{
    partial class WebDocument : DockContent
    {

        #region Fields
        private const String JS_FAVICON = "(function(){links = document.getElementsByTagName('link'); wHref=window.location.protocol + '//' + window.location.hostname + '/favicon.ico'; for(i=0; i<links.length; i++){s=links[i].rel; if(s.indexOf('icon') != -1){ wHref = links[i].href }; }; return wHref; })();";
        private bool goToHome, fixedUrl;
        #endregion


        #region Ctors
        public WebDocument()
        {
            InitializeComponent();
            goToHome = true;
        }

        public WebDocument( string url )
        {
            InitializeComponent();
            webControl.Source = new Uri( url );
        }

        public WebDocument( string url, string title )
        {
            InitializeComponent();
            webControl.Source = new Uri( url );
            fixedUrl = true;
            this.Text = title;
            //this.toolStripButton4.Visible = false;
            //this.toolStripButton5.Visible = false;
            //this.addressBox.ReadOnly = true;
            this.pageToolStrip.Visible = false;
        }
        #endregion


        #region Methods
        protected override void OnLoad( EventArgs e )
        {
            base.OnLoad( e );

            cutToolStripMenuItem.Enabled = webControl.HasKeyboardFocus && webControl.HasSelection;
            copyToolStripMenuItem.Enabled = webControl.HasSelection;
            copyHTMLToolStripMenuItem.Enabled = webControl.HasSelection;
            pasteToolStripMenuItem.Enabled = webControl.HasKeyboardFocus && Clipboard.ContainsText();

            // For this application we can activate self-update
            // (see WebControl.SelfUpdate). The Windows Forms WebControl
            // does not internally control the IsRendering property but
            // since we are planning to display our controls in a tabbed 
            // dock-manager, we can pause and resume rendering ourselves.
            webControl.SelfUpdate = true;

            if ( goToHome )
                webControl.GoToHome();
        }

        protected override void OnDockStateChanged( EventArgs e )
        {
            base.OnDockStateChanged( e );

            switch ( this.DockState )
            {
                case DockState.Hidden:
                    // Pause rendering when this window is hidden.
                    webControl.IsRendering = false;
                    break;

                case DockState.Document:
                    // A non-activated document, should be covered
                    // by others since it is displayed in a tab-control;
                    // we can safely pause rendering when it is not activated.
                    // This is not the case with tool-windows docked on
                    // the sides of the main window that may be visible while not activated;
                    // this is why we default to true for all other scenarios below.
                    webControl.IsRendering = this.IsActivated;
                    break;

                default:
                    webControl.IsRendering = true;
                    break;
            }
        }

        protected override void OnGotFocus( EventArgs e )
        {
            base.OnGotFocus( e );
            // Transfer focus to the control when the
            // tab acquires it.
            webControl.Focus();
        }

        protected override void OnFormClosed( FormClosedEventArgs e )
        {
            webControl.Close();
            base.OnFormClosed( e );
        }

        private void UpdateFavicon()
        {
            // Execute some simple javascript that will search for a favicon.
            using ( JSValue val = webControl.ExecuteJavascriptWithResult( JS_FAVICON ) )
            {
                // Check if we got a valid answer.
                if ( ( val != null ) && ( val.Type == JSValueType.String ) )
                {
                    // We do not need to perform the download of the favicon synchronously.
                    // May be a full icon set (thus big).
                    Task.Factory.StartNew<Bitmap>( GetFavicon, val.ToString() ).ContinueWith(
                        ( t ) =>
                        {
                            // If the download completed successfully, set the new favicon.
                            // This post-completion procedure is executed synchronously.
                            if ( ( t.Exception == null ) && ( t.Result != null ) )
                            {
                                this.Icon = Icon.FromHandle( t.Result.GetHicon() );
                                this.DockPanel.Refresh();
                            }
                        },
                        TaskScheduler.FromCurrentSynchronizationContext() );
                }
            }
        }

        private static Bitmap GetFavicon( Object href )
        {
            using ( WebClient client = new WebClient() )
            {
                Byte[] data = client.DownloadData( href.ToString() );

                if ( ( data != null ) && ( data.Length > 0 ) )
                    return new Bitmap( new MemoryStream( data ) );
            }

            return null;
        }

        private void QueryDownload( string url )
        {
            try
            {
                // Create a request for the headers only.
                WebRequest request = WebRequest.Create( url );
                request.Method = WebRequestMethods.Http.Head;

                // Asynchronously perform the request.
                request.BeginGetResponse( ( ar ) =>
                {
                    try
                    {
                        WebResponse response = request.EndGetResponse( ar );

                        if ( response != null )
                        {
                            // Initialize.
                            string fileName = string.Empty;

                            // Get the "content-disposition" header.
                            // We currently care for the filename only. However, at this point we could
                            // also inform the user of the size of the file by getting the value of the:
                            // "content-length" header.
                            string contentDisposition = response.Headers[ "content-disposition" ];

                            if ( !String.IsNullOrEmpty( contentDisposition ) )
                            {
                                // Look for the filename part.
                                string fileNamePart = "filename=";
                                int index = contentDisposition.IndexOf( fileNamePart, StringComparison.InvariantCultureIgnoreCase );

                                if ( index >= 0 )
                                {
                                    fileName = contentDisposition.Substring( index + fileNamePart.Length );
                                    // Cleanup any invalid surrounding characters.
                                    fileName = fileName.Trim( Path.GetInvalidFileNameChars() ).Trim( new char[] { '"', ';' } );
                                }
                            }

                            // No filename suggested or the resource is actually a stream,
                            // served by default with no "Content-Disposition".
                            if ( String.IsNullOrWhiteSpace( fileName ) )
                            {
                                // Attempt to get the filename from the URL's last segment.
                                if ( response.ResponseUri.Segments.Length >= 0 )
                                {
                                    int fileNamePos = response.ResponseUri.Segments.Length - 1;
                                    fileName = response.ResponseUri.Segments[ fileNamePos ];
                                }
                            }

                            // If we have a filename, proceed with asking the user
                            // and performing the actual download.
                            if ( !String.IsNullOrWhiteSpace( fileName ) )
                                this.BeginInvoke( (Action<string, string>)RequestDownload, url, fileName );
                        }
                    }
                    catch { }
                }, null );
            }
            catch { }
        }

        private void RequestDownload( string url, string fileName )
        {
            MainForm mainForm = this.MainForm;

            if ( mainForm == null )
                return;

            using ( SaveFileDialog dialog = new SaveFileDialog()
            {
                FileName = fileName,
                // We set MyDocuments as the default. You can change this as you wish
                // but make sure the specified folder actually exists.
                InitialDirectory = Environment.GetFolderPath( Environment.SpecialFolder.MyDocuments )
            } )
            {
                if ( ( dialog.ShowDialog( mainForm ) == DialogResult.OK ) && ( dialog.FileNames.Length == 1 ) )
                    mainForm.DownloadFile( url, dialog.FileName );
            }
        }
        #endregion

        #region Properties
        public MainForm MainForm
        {
            get
            {
                if ( this.DockPanel != null )
                    return this.DockPanel.FindForm() as MainForm;

                return null;
            }
        }

        public bool IsRendering
        {
            get { return webControl.IsRendering; }
            set { webControl.IsRendering = value; }
        }
        #endregion

        #region Event Handlers

        #region WebControl
        private void webControl_OpenExternalLink( object sender, OpenExternalLinkEventArgs e )
        {
            if ( this.DockPanel != null )
            {
                WebDocument doc = new WebDocument( e.Url );
                doc.Show( this.DockPanel );
            }
        }

        private void webControl_DomReady( object sender, EventArgs e )
        {
            // DOM is ready. We can start looking for a favicon.
            UpdateFavicon();
        }

        private void webControl_TargetUrlChanged( object sender, UrlEventArgs e )
        {
            MainForm mainForm = this.MainForm;

            if ( mainForm != null )
                mainForm.Status = e.Url;
        }

        private void webControl_PropertyChanged( object sender, PropertyChangedEventArgs e )
        {
            MainForm mainForm = this.MainForm;

            switch ( e.PropertyName )
            {
                case "Title":
                    if ( fixedUrl )
                        break;

                    this.Text = webControl.Title;

                    if ( mainForm != null )
                        mainForm.Title = this.Text;

                    break;
                case "IsLoadingPage":
                    if ( mainForm != null )
                        mainForm.ShowProgress = webControl.IsLoadingPage;

                    break;

                case "HasSelection":
                    if ( !webControl.HasSelection )
                    {
                        cutToolStripMenuItem.Enabled = false;
                        copyToolStripMenuItem.Enabled = false;
                        copyHTMLToolStripMenuItem.Enabled = false;
                    }
                    else
                    {
                        cutToolStripMenuItem.Enabled = webControl.HasKeyboardFocus;
                        copyToolStripMenuItem.Enabled = true;
                        copyHTMLToolStripMenuItem.Enabled = true;
                    }
                    break;

                case "HasKeyboardFocus":
                    pasteToolStripMenuItem.Enabled = webControl.HasKeyboardFocus && Clipboard.ContainsText();
                    break;

                case "IsCrashed":
                    bool isLive = !webControl.IsCrashed;

                    copyToolStripMenuItem.Enabled = isLive;
                    cutToolStripMenuItem.Enabled = isLive;
                    copyHTMLToolStripMenuItem.Enabled = isLive;
                    pasteToolStripMenuItem.Enabled = isLive;
                    refreshToolStripMenuItem.Enabled = isLive;
                    printToolStripMenuItem.Enabled = isLive;
                    selectAllToolStripMenuItem.Enabled = isLive;

                    break;

                default:
                    break;
            }
        }

        private void webControl_BeginLoading( object sender, BeginLoadingEventArgs e )
        {
            // By now we have already navigated to the address.
            // Clear the old favicon.
            if ( this.Icon != null )
                this.Icon.Dispose();

            ComponentResourceManager resources = new ComponentResourceManager( typeof( WebDocument ) );
            this.Icon = ( (Icon)( resources.GetObject( "$this.Icon" ) ) );
            this.DockPanel.Refresh();
        }

        private void webControl_SelectLocalFiles( object sender, SelectLocalFilesEventArgs e )
        {
            using ( OpenFileDialog dialog = new OpenFileDialog()
            {
                Title = e.Title,
                InitialDirectory = Environment.GetFolderPath( Environment.SpecialFolder.MyDocuments ),
                CheckFileExists = true,
                Multiselect = e.SelectMultipleFiles
            } )
            {
                if ( ( dialog.ShowDialog( this.MainForm ) == DialogResult.OK ) && ( dialog.FileNames.Length > 0 ) )
                    e.SelectedFiles = dialog.FileNames;
                else
                    e.Cancel = true;
            }
        }

        private void webControl_Download( object sender, UrlEventArgs e )
        {
            // For the time being, the event provides the URL (that may contain a query) but not
            // the actual filename. We will query the server for the name of the file.
            // (Some browsers, like Chrome, immediately start the download while they figure out
            // the filename and then wait for the user to respond to the Save dialog. This is why
            // when you actually hit "Save", you notice that most of the file is already downloaded)
            QueryDownload( e.Url );
        }
        #endregion

        #region Toolstrip / Menu
        private void closeTabToolStripMenuItem_Click( object sender, EventArgs e )
        {
            this.Close();
        }

        private void closeOtherTabsToolStripMenuItem_Click( object sender, EventArgs e )
        {
            var contents = from content in this.DockPanel.Contents
                           where content is WebDocument
                           where content != this
                           select content as WebDocument;

            List<WebDocument> list = new List<WebDocument>( contents );
            list.ForEach( c => c.Close() );
        }

        private void toolStripButton1_MouseEnter( object sender, EventArgs e )
        {
            ( (ToolStripButton)sender ).ImageIndex += 1;
        }

        private void toolStripButton1_MouseLeave( object sender, EventArgs e )
        {
            ( (ToolStripButton)sender ).ImageIndex -= 1;
        }

        private void toolStripButton1_Click( object sender, EventArgs e )
        {
            if ( webControl.IsCrashed )
                return;

            webControl.GoBack();
        }

        private void toolStripButton2_Click( object sender, EventArgs e )
        {
            if ( webControl.IsCrashed )
                return;

            webControl.GoForward();
        }

        private void toolStripButton3_Click( object sender, EventArgs e )
        {
            if ( webControl.IsCrashed )
                return;

            webControl.Reload();
        }

        private void toolStripButton4_Click( object sender, EventArgs e )
        {
            webControl.GoToHome();
        }

        private void toolStripButton5_Click( object sender, EventArgs e )
        {
            MainForm mainForm = this.MainForm;

            if ( mainForm == null )
                return;

            mainForm.DownloadsWindow.Show( this.DockPanel );
        }

        private void newTabToolStripMenuItem_Click( object sender, EventArgs e )
        {
            if ( this.DockPanel != null )
            {
                WebDocument doc = new WebDocument();
                doc.Show( this.DockPanel );
            }
        }

        private void refreshToolStripMenuItem_Click( object sender, EventArgs e )
        {
            webControl.Reload();
        }


        private void printToolStripMenuItem_Click( object sender, EventArgs e )
        {
            if ( webControl.IsCrashed )
                return;

            webControl.Print();
        }

        private void cutToolStripMenuItem_Click( object sender, EventArgs e )
        {
            if ( addressBox.Focused )
                addressBox.Cut();
            else if ( !webControl.IsCrashed )
                webControl.Cut();
        }

        private void copyToolStripMenuItem_Click( object sender, EventArgs e )
        {
            if ( addressBox.Focused )
                addressBox.Copy();
            else if ( !webControl.IsCrashed )
                webControl.Copy();
        }

        private void copyHTMLToolStripMenuItem_Click( object sender, EventArgs e )
        {
            if ( webControl.IsCrashed )
                return;

            if ( webControl.HasSelection )
                webControl.CopyHTML();
        }

        private void pasteToolStripMenuItem_Click( object sender, EventArgs e )
        {
            if ( addressBox.Focused )
                addressBox.Paste();
            else if ( !webControl.IsCrashed )
                webControl.Paste();
        }

        private void selectAllToolStripMenuItem_Click( object sender, EventArgs e )
        {
            if ( addressBox.Focused )
                addressBox.SelectAll();
            else if ( !webControl.IsCrashed )
                webControl.SelectAll();
        }

        private void customizeToolStripMenuItem_Click( object sender, EventArgs e )
        {
            MessageBox.Show( this, "Not implemented in this sample." );
        }

        private void saveToolStripMenuItem_Click( object sender, EventArgs e )
        {
            MessageBox.Show( this, "Not implemented in this sample." );
        }
        #endregion

        #endregion
    }
}
